#include <cutils/buffer.h>
#include "lua_f_base.h"

#define BUFFER_METATABLE_NAME "CUtils.Buffer.MT"

#define check_buffer(L) (buffer_t *)luaL_checkudata(L, 1, BUFFER_METATABLE_NAME)


static int lua_f_buffer_close(lua_State *L)
{
    buffer_t *self = check_buffer(L);
    //buffer_destroy(&self);
	buffer_term(self);
    return 0;
}

static int lua_f_buffer_add(lua_State *L)
{
    int top = lua_gettop(L);
    if(top < 2){
        RETERR("No enought args");
    }
    
    buffer_t *self = check_buffer(L);
    size_t len = 0;
    const char *data = lua_tolstring(L, 2, &len);
    //DBG("len: %u", len);
    if(len == 0){
        RETERR("invalid len");
    }
    
    if(buffer_add(self, (uint8_t *)data, len) < 0){
        RETERR("buffer_add fail");
    }

    lua_pushinteger(L, len);
    return 1;
}

static int _lua_f_buffer_getall(buffer_t *self, lua_State *L)
{
    size_t len = buffer_len(self);
    if(len == 0){ // no data
        lua_pushnil(L);
    }else{
        uint8_t *data = buffer_data(self);
        lua_pushlstring(L, (const char *)data, len);
        buffer_drain(self, len);
    }
    
    return 1;
}

static int _lua_f_buffer_getline(buffer_t *self, lua_State *L)
{
    size_t len = 0;
    uint8_t *line = buffer_getline(self, &len);
    if(line == NULL){
        lua_pushnil(L);
    }else{
        lua_pushlstring(L, (const char *)line, len);
        safe_free(line);
    }

    return 1;
}

static int lua_f_buffer_get(lua_State *L)
{
    buffer_t *self = check_buffer(L);
    if(!lua_isnumber(L, 2)){
        const char *p = luaL_optstring(L, 2, "*l");
        if(p[0] == '*' && p[1] == 'l'){
            return _lua_f_buffer_getline(self, L);
        }else if(p[0] == '*' && p[1] == 'a'){
            return _lua_f_buffer_getall(self, L);
        }else{
            RETERR("invalid read pattern");
        }
    }else{
        size_t len = lua_tonumber(L, 2);
        if(0 == len || 0 == buffer_len(self)){
            //RETERR("invalid len");
            lua_pushnil(L);
            return 1;
        }

        len = MIN(len, buffer_len(self));
        lua_pushlstring(L, (const char *)(buffer_data(self)), len);
        buffer_drain(self, len);
        return 1;
    } 
}

static int lua_f_buffer_data(lua_State *L)
{
    buffer_t *self = check_buffer(L);
    return _lua_f_buffer_getall(self, L);
}

static int lua_f_buffer_len(lua_State *L)
{
    buffer_t *self = check_buffer(L);
    lua_pushinteger(L, buffer_len(self));
    return 1;
}

static int lua_f_buffer_clean(lua_State *L)
{
    buffer_t *self = check_buffer(L);
    if(buffer_clean(self) < 0)
        lua_pushboolean(L, 0);
    else
        lua_pushboolean(L, 1);

    return 1;
}

static int lua_f_buffer_read(lua_State *L)
{
    if(lua_gettop(L) < 2){
        RETERR("invalid args");
    }

    buffer_t *self = check_buffer(L);
    int fd = lua_tointeger(L, 2);
    size_t size = luaL_optint(L, 3, 128);// default is 128 bytes

    if(fd < 0){
        RETERR("invalid fd");
    }

    if(size == 0){
        RETERR("invalid size");
    }

    int ret = buffer_from_fd(self, fd, size);
    if(ret < 0){
        RETERR("buffer_read fail");
    }

    lua_pushinteger(L, ret);
    return 1;
}

static int lua_f_buffer_write(lua_State *L)
{
    if(lua_gettop(L) < 2){
        RETERR("invalid args");
    }

    buffer_t *self = check_buffer(L);
    int fd = lua_tointeger(L, 2);
    size_t size = luaL_optint(L, 3, buffer_len(self));
    size = MIN(size, buffer_len(self));
    
    if(fd < 0){
        RETERR("invalid fd");
    }

    if(size == 0){
        lua_pushinteger(L, size);
        return 1;
    }

    int ret = buffer_to_fd(self, fd, size);
    if(ret < 0){
        RETERR("buffer_write fail");
    }

    lua_pushinteger(L, ret);
    return 1;
}


static const struct luaL_Reg lua_f_buffer_func[] = {
    {"close", lua_f_buffer_close},
    {"add", lua_f_buffer_add},
    {"get", lua_f_buffer_get},
    {"read", lua_f_buffer_read},
    {"write", lua_f_buffer_write},
    {"length", lua_f_buffer_len},
    {"clean", lua_f_buffer_clean},
    {NULL, NULL},
};


static int lua_f_buffer_create(lua_State *L)
{
    buffer_t *self = (buffer_t *)lua_newuserdata(L, sizeof(buffer_t));
	if (!self)
		RETERR("lua_newuserdata fail");

    if (buffer_init(self, 0) < 0)
        RETERR("buffer_init fail");

    luaL_getmetatable(L, BUFFER_METATABLE_NAME);
    lua_setmetatable(L, -2);
    return 1;
}

static int lua_f_buffer_version(lua_State *L)
{
    const char *ver = "Buffer V0.0.1";
    lua_pushstring(L, ver);
    return 1;
}

static const struct luaL_Reg lua_f_buffer_mod[] = {
    {"new", lua_f_buffer_create},
    {"version", lua_f_buffer_version},
    {NULL, NULL},
};

/*
 mt = {
    __gc = lua_f_buffer_close,
    __len = lua_f_buffer_len,
    __tostring = lua_f_buffer_data,
    __index = {
        close = lua_f_buffer_close,
        add = lua_f_buffer_add,
        get = lua_f_buffer_get,
        read = lua_f_buffer_read,
        write = lua_f_buffer_write,
        length = lua_f_buffer_len,
        clean = lua_f_buffer_clean,
    },
 };

 buffer = {
    new = lua_f_buffer_create,
    version = lua_f_buffer_version,
 };
 
*/
int luaopen_buffer(lua_State *L){
    luaL_newmetatable(L, BUFFER_METATABLE_NAME);
    lua_pushcfunction(L, lua_f_buffer_close);
    lua_setfield(L, -2, "__gc");
    lua_pushcfunction(L, lua_f_buffer_len);
    lua_setfield(L, -2, "__len");
    lua_pushcfunction(L, lua_f_buffer_data);
    lua_setfield(L, -2, "__tostring");
    lua_newtable(L);
    luaL_register(L, NULL, lua_f_buffer_func);
    lua_setfield(L, -2, "__index");
    lua_pop(L, 1);
    
    luaL_register(L, "buffer", lua_f_buffer_mod);
    return 1;
}

